package com.bbs.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.PhoneUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bbs.dto.LoginFormDTO;
import com.bbs.dto.Result;
import com.bbs.dto.UserDTO;
import com.bbs.dto.UserQueryDTO;
import com.bbs.entity.User;
import com.bbs.event.LoginSuccessEvent;
import com.bbs.factory.EventFactory;
import com.bbs.factory.achieve.LoginSuccessEventFactory;
import com.bbs.mapper.UserMapper;
import com.bbs.service.UserService;
import com.bbs.util.UserHolder;
import com.bbs.vo.UserVO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import com.bbs.publish.EventPublisher;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.bbs.util.constant.*;

@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Autowired
    private PasswordEncoder passwordEncoder; // 注入配置的Bean
    @Autowired
    EventPublisher eventPublisher;
    @Autowired
    EventFactory<UserDTO> loginSuccessEvent;
@Resource
private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private LoginSuccessEventFactory loginSuccessEventFactory;

    @Override
    public Result sendCode(String phone) {
        boolean isPhone = PhoneUtil.isPhone(phone);
        if(!isPhone){
            return Result.fail("手机号输入格式错误");
        }
        String code = RandomUtil.randomNumbers(6);
        log.info("验证码:{}",code);
        //将手机号和验证码存到缓存，60s
        stringRedisTemplate.opsForValue().set(CODE_PRE_KEY+phone,code,CODE_TIME, TimeUnit.SECONDS);
        return Result.ok();
    }

    @Override
    public Result loginByCode(LoginFormDTO loginFormDTO) {
        //TODO: 考虑添加登录失败次数限制
        //TODO: 考虑添加图形验证码，防止短信验证码被刷
        
        String phone = loginFormDTO.getPhone();
        //验证手机号格式
        boolean isPhone = PhoneUtil.isPhone(phone);
        if(!isPhone){
            return Result.fail("手机号格式错误");
        }
        
        //验证验证码
        String code1 = loginFormDTO.getCode();
        if(StrUtil.isBlank(code1))return Result.fail("验证码错误");
        String code = stringRedisTemplate.opsForValue().get(CODE_PRE_KEY + phone);
        if(!code1.equals(code)){
            return Result.fail("验证码错误");
        }
        
        //删除验证码
        stringRedisTemplate.delete(CODE_PRE_KEY + phone);

        //查询用户是否存在
        User user = query().eq("phone",phone).one();
        
        //用户不存在，自动注册
        if(user==null) {
            User newUser = new User();
            newUser.setPhone(phone);
            //生成随机用户名
            String nameEnd = UUID.randomUUID().toString().substring(0, 8);
            newUser.setUsername(USER_NAME_PRE + nameEnd);
            save(newUser);
            user = newUser;
        }
        
        //TODO: 考虑添加用户状态检查（是否被封禁等）
        
        //转换为DTO
        UserDTO userDTO = new UserDTO();
        BeanUtil.copyProperties(user, userDTO);
        
        //将用户信息转换为Map，用于Redis存储
        Map<String,Object> userMap = BeanUtil.beanToMap(
                userDTO,
                new HashMap<>(),
                CopyOptions.create()
                    .setIgnoreNullValue(true)
                    .setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString())
        );
        
        //生成token
        String token = UUID.randomUUID().toString(true);
        String tokenKey = TOKEN_PRE + token;
        
        //存储用户信息到Redis
        stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);

        //发布事件

        //TODO: 考虑添加登录日志记录
        //TODO: 考虑添加登录IP记录
//通过事件工厂获取事件
        LoginSuccessEvent event = (LoginSuccessEvent) loginSuccessEventFactory.createEvent(new Object(), userDTO);
        //发布事件
eventPublisher.sendEvent(event);
        //返回token给前端
        return Result.ok(tokenKey);
    }




    @Override
    public UserVO createUser(UserDTO userDTO) {
        User user = new User();
        BeanUtils.copyProperties(userDTO, user);
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        save(user);
        return convertToVO(user);
    }
    
    @Override
    public UserVO getUserById(Long id) {
        User user = getById(id);
        return convertToVO(user);
    }
    
    @Override
    public List<UserVO> getAllUsers() {
        List<User> users = list();
        return users.stream()
                .map(this::convertToVO)
                .collect(Collectors.toList());
    }
    
    @Override
    public UserVO updateUser(Long id, UserDTO userDTO) {
        User user = getById(id);
        BeanUtils.copyProperties(userDTO, user);
        updateById(user);
        return convertToVO(user);
    }
    
    @Override
    public void deleteUser(Long id) {
        removeById(id);
    }
    
    @Override
    public UserVO getUserByUsername(String username) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, username);
        User user = getOne(wrapper);
        return convertToVO(user);
    }


    private UserVO convertToVO(User user) {
        if (user == null) {
            return null;
        }
        UserVO vo = new UserVO();
        BeanUtils.copyProperties(user, vo);
        return vo;
    }
    @Override
    public Result sign() {
       //获取用户Id
        Long userId = UserHolder.getUser().getId();

        //获取当天天数
        LocalDateTime now=LocalDateTime.now();
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMM");//获取当前年月，用于对应该月签到表
        String keyPre = now.format(dateTimeFormatter);
        String key=USER_LOGIN+userId+keyPre;
        int dayOfMonth = now.getDayOfMonth();
        //插入
        stringRedisTemplate.opsForValue().setBit(key,dayOfMonth-1,true);
        return Result.ok();
    }

    @Override
    public Integer signCount() {
        //获取天数数据
        LocalDateTime now=LocalDateTime.now();
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMM");//获取当前年月，用于对应该月签到表
        String keyPre = now.format(dateTimeFormatter);
        int dayOfMonth = now.getDayOfMonth();
        //获取用户Id
        Long userId = UserHolder.getUser().getId();
        String key=USER_LOGIN+userId+keyPre;
        //插入
        List<Long> res = stringRedisTemplate.opsForValue().bitField(
                key,
                BitFieldSubCommands.create()
                        .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
        );
        //由于我们查询一个位图，返回的就是该用户当月的签到位图
        if(res==null||res.isEmpty()){
            return 0;
        }
        Long num = res.get(0);
        int count=0;
        while(true){
            if((num&1)==0){
                break;
            }
            else{
               count++;
            }
            num>>=1;
        }
        return count;
    }

    @Override
    public Result queryUsers(UserQueryDTO userQueryDTO) {
        String username = userQueryDTO.getUsername();
        Long id = userQueryDTO.getId();
        String phone = userQueryDTO.getPhone();
        List<User> userList = lambdaQuery().eq(id != null, User::getId, id)
                .like(username != null, User::getUsername, username)
                .eq(phone != null, User::getPhone, phone)
                .list();

        List<UserDTO> res = userList.stream().map(user -> {
            UserDTO userDTO = new UserDTO();
            BeanUtil.copyProperties(user, userDTO);
            return userDTO;
        }).collect(Collectors.toList());
        return Result.ok(res);
    }

} 